package git.soulbgm.common.base;

import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

/**
 * 此类为设置多数据源的抽象类已有实现之需加上
 * - @Bean("bean名称")
 * - @Primary 如果该数据源是主数据源则加此注解
 * 在dataSource方法上加此注解
 * - @ConfigurationProperties(prefix = "数据源的开头")
 * 在class的头部加此注解
 * - @MapperScan(basePackages = "扫描的包", sqlSessionTemplateRef = "SqlSessionTemplate的Bean名称")
 * 有多个数据源时Quartz无法自动分配数据源这时可以使用@QuartzDataSource注解来标记来使用那个数据源
 *
 * @author SoulBGM
 * @date 2022-11-24
 */
public abstract class BaseDataSourceConfig {

    protected final MybatisPlusProperties properties;
    private final ApplicationContext applicationContext;

    public BaseDataSourceConfig(MybatisPlusProperties properties, ApplicationContext applicationContext) {
        this.properties = properties;
        this.applicationContext = applicationContext;
    }

    /**
     * 通过BeanCopier来克隆从MybatisProperties.getConfiguration()中取出的Configuration对象
     *
     * @return Configuration对象
     */
    protected MybatisConfiguration getNewConfiguration() {
        MybatisPlusProperties.CoreConfiguration conf = properties.getConfiguration();
        MybatisConfiguration newConf = new MybatisConfiguration();
        BeanCopier copier = BeanCopier.create(conf.getClass(), newConf.getClass(), false);
        copier.copy(conf, newConf, null);

        return newConf;
    }

    /**
     * 资源分解器
     */
    private static final ResourcePatternResolver RESOURCE_RESOLVER = new PathMatchingResourcePatternResolver();

    /**
     * 获取解析映射器位置
     *
     * @return Resource数组
     */
    protected Resource[] getResolveMapperLocations() {
        try {
            return RESOURCE_RESOLVER.getResources(getMapperLocations());
        } catch (IOException e) {
            return new Resource[0];
        }
    }

    /**
     * 获得mapper.xml的存放位置
     *
     * @return {@link String}
     */
    public abstract String getMapperLocations();

    protected DataSource dataSource;

    protected SqlSessionFactory sqlSessionFactory;

    protected DataSourceTransactionManager dataSourceTransactionManager;

    protected SqlSessionTemplate sqlSessionTemplate;

    /*@Bean("primaryDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.primary")*/
    public DataSource dataSource() {
        //return dataSource = DruidDataSourceBuilder.create().build();
        return null;
    }

    /*@Bean("primarySqlSessionFactory")
    @Primary*/
    public SqlSessionFactory sqlSessionFactory() {
        if (dataSource == null) {
            dataSource();
        }
        // 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        // 通过实现getMapperLocations方法返回每个数据源对应的mapper.xml的路径地址
        factory.setMapperLocations(getResolveMapperLocations());
        // 沿用配置文件中的mybatis的配置
        factory.setConfiguration(getNewConfiguration());
        GlobalConfig globalConfig = this.properties.getGlobalConfig();
        // 注入填充器
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        // 注入主键生成器
        this.getBeansThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerators(i));
        // 注入sql注入器
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        // 注入ID生成器
        this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
        // 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
        factory.setGlobalConfig(globalConfig);
        // 设置拦截器
        this.getBeanThen(MybatisPlusInterceptor.class, factory::setPlugins);
        try {
            return sqlSessionFactory = factory.getObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {
        if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
            consumer.accept(this.applicationContext.getBean(clazz));
        }
    }

    private <T> void getBeansThen(Class<T> clazz, Consumer<List<T>> consumer) {
        if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
            Map<String, T> beansOfType = this.applicationContext.getBeansOfType(clazz);
            List<T> clazzList = new ArrayList<>();
            beansOfType.forEach((k, v) -> clazzList.add(v));
            consumer.accept(clazzList);
        }
    }

    /*@Bean("primaryTransactionManager")
    @Primary*/
    public DataSourceTransactionManager transactionManager() {
        if (dataSource == null) {
            dataSource();
        }
        return dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
    }

    /*@Bean("primarySqlSessionTemplate")
    @Primary*/
    public SqlSessionTemplate sqlSessionTemplate() {
        if (sqlSessionFactory == null) {
            sqlSessionFactory();
        }
        return sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
    }

}